package ch.nostromo.tiffanys.engines.tiffanys;

import java.util.logging.Logger;

import uk.ac.cam.cl.projectcharlie2010.engines.Engine;
import uk.ac.cam.cl.projectcharlie2010.game.GameController;

import ch.nostromo.tiffanys.engines.EngineException;
import ch.nostromo.tiffanys.engines.tiffanys.ai.Evaluation;
import ch.nostromo.tiffanys.engines.tiffanys.ai.TranspositionTableTiff;
import ch.nostromo.tiffanys.engines.tiffanys.board.TiffanysBoard;
import ch.nostromo.tiffanys.engines.tiffanys.board.TiffanysMove;

public class TiffanysAlphaBetaRunnable implements Runnable {
  static Logger logger = Logger.getLogger(GameController.class.getName());

  public static int VALUE_EXACT = 0;
  public static int VALUE_LOWER = 1;
  public static int VALUE_UPPER = 2;

  private int depth;
  private int initAlpha;
  private int initBeta;
  private long[] board;
  private int colorToMove;
  private boolean fromHitMove;
  private int currentPlannedSearchDepth;

  private TiffanysMove[][] movesBuffer = new TiffanysMove[100][100];
  private int movesBufferDepth = 0;

  private TiffanysMove move;

  private TiffanysEngine engine;

  public TiffanysAlphaBetaRunnable(TiffanysEngine engine, TiffanysMove move, int depth, int alpha, int beta, long[] board, int colorToMove, boolean fromHitMove, int currentPlannedSearchDepth) {
    this.engine = engine;
    this.move = move;
    this.depth = depth;
    this.initAlpha = alpha;
    this.initBeta = beta;
    this.board = board;
    this.colorToMove = colorToMove;
    this.fromHitMove = fromHitMove;
    this.currentPlannedSearchDepth = currentPlannedSearchDepth;
    this.movesBufferDepth = 0;
    for (int i = 0; i < movesBuffer.length; i++) {
      for (int j = 0; j < movesBuffer[i].length; j++) {
        movesBuffer[i][j] = new TiffanysMove();
      }
    }
  }

  public void run() {

    try {
      this.move.score = -alphaBeta(this.depth, this.initAlpha, this.initBeta, this.colorToMove, this.fromHitMove);
      this.move.scoreDepth = this.currentPlannedSearchDepth;
    } catch (Exception ignore) {
      if (!(ignore instanceof EngineException)) {
        logger.severe(ignore.getMessage());
      }
    }

  }

  private int quiescentSearch(int alpha, int beta, int colorToMove) {
    engine.statNodes++;
    engine.statPositionsEvaluated++;

    int value = Evaluation.evaluatePosition(board, colorToMove);

    if (value >= beta) {
      engine.statAbCutOffs++;
      return beta;
    }

    if (value > alpha) {
      alpha = value;
    }

    TiffanysMove[] moves = movesBuffer[movesBufferDepth];

    TiffanysBoard.generateHitMoveList(moves, board, colorToMove);
    bubbleSortMoves(moves);

    for (int i = 0; i < moves.length; i++) {

      if (moves[i].isSet) {

        TiffanysBoard.makeMove(board, moves[i], colorToMove);

        boolean isCheck = TiffanysBoard.checkForCheck(board, colorToMove);
        if (!isCheck) {

          movesBufferDepth++;
          value = -quiescentSearch(-beta, -alpha, TiffanysBoard.invertColor(colorToMove));
          movesBufferDepth--;

          if (value >= beta) {
            TiffanysBoard.takeBackMove(board, moves[i]);
            engine.statAbCutOffs++;
            return beta;
          }

          if (value > alpha) {
            alpha = value;
          }

        }

        TiffanysBoard.takeBackMove(board, moves[i]);

      }
    }

    return alpha;
  }

  private int alphaBeta(int depth, int alpha, int beta, int colorToMove, boolean fromHitMove) throws EngineException {

    if (!engine.running && TiffanysEngine.abortable) {
      throw new EngineException("Abort search");
    }

    engine.statNodes++;

    int currentRelativeSearchDepth = this.currentPlannedSearchDepth - depth;

    long boardx = board[TiffanysBoard.IDX_ZOBRIST_KEY];
    if (boardx < 0) {
      boardx = boardx * -1;
    }

    int hashIdx = TranspositionTableTiff.getBoardIdx(boardx);
    if (hashIdx > 0 && TranspositionTableTiff.depths[hashIdx] >= depth) {
      int value = TranspositionTableTiff.values[hashIdx];
      int mode = TranspositionTableTiff.modes[hashIdx];
      if (mode == VALUE_EXACT) {// stored value is exact
        return value;
      }
      if (mode == VALUE_LOWER && value > alpha) {
        alpha = value; // update lowerbound alpha if needed
      } else if (mode == VALUE_UPPER && value < beta) {
        beta = value; // update upperbound beta if needed
      }
      if (alpha >= beta) {
        return value; // if lowerbound surpasses upperbound
      }
    }

    if (depth == 0) {
      int value = 0;
      if (fromHitMove && currentPlannedSearchDepth > 2) {
        movesBufferDepth++;
        value = quiescentSearch(alpha, beta, colorToMove);
        movesBufferDepth--;
      } else {
        engine.statPositionsEvaluated++;
        value = Evaluation.evaluatePosition(board, colorToMove);
      }

        if (!TranspositionTableTiff.full) {

          if (value <= alpha) {// a lowerbound value
            TranspositionTableTiff.addBoard(boardx, value, depth, VALUE_LOWER);
          } else if (value >= beta) {// an upperbound value
            TranspositionTableTiff.addBoard(boardx, value, depth, VALUE_UPPER);
          } else {
            // a true minimax value
            TranspositionTableTiff.addBoard(boardx, value, depth, VALUE_EXACT);
          }
        }

      return value;
    }

    TiffanysMove[] moves = movesBuffer[movesBufferDepth];

    TiffanysBoard.generateMoveList(moves, board, colorToMove);

    bubbleSortMoves(moves);

    int val = 0;
    int best = -Engine.MAX_RANGE + currentRelativeSearchDepth;
    boolean legalMovefound = false;

    for (int i = 0; i < moves.length; i++) {

      if (moves[i].isSet) {

        TiffanysBoard.makeMove(board, moves[i], colorToMove);

        boolean isCheck = TiffanysBoard.checkForCheck(board, colorToMove);

        if (!isCheck) {
          legalMovefound = true;

          movesBufferDepth++;
          val = -alphaBeta(depth - 1, -beta, -alpha, TiffanysBoard.invertColor(colorToMove), moves[i].hitmove);
          movesBufferDepth--;

          TiffanysBoard.takeBackMove(board, moves[i]);

          if (val > best) {
            best = val;
          }
          if (best > alpha) {
            alpha = best;
          }
          if (best >= beta) {
            engine.statAbCutOffs++;
            break;
          }
        } else {
          TiffanysBoard.takeBackMove(board, moves[i]);
        }

      }
    }

    if (!legalMovefound) {
      if (TiffanysBoard.checkForCheck(board, colorToMove)) {
        engine.statPositionsEvaluated++;
        return -Engine.MAX_RANGE + currentRelativeSearchDepth;
      } else {
        engine.statPositionsEvaluated++;
        return 0;
      }
    }

      if (!TranspositionTableTiff.full) {
        if (best <= alpha) { // a lowerbound value
          TranspositionTableTiff.addBoard(boardx, best, depth, VALUE_LOWER);
        } else if (best >= beta) {// an upperbound value
          TranspositionTableTiff.addBoard(boardx, best, depth, VALUE_UPPER);
        } else {
          // a true minimax value
          TranspositionTableTiff.addBoard(boardx, best, depth, VALUE_EXACT);
        }
      }

      return best;
  }

  public static void bubbleSortMoves(TiffanysMove[] toSort) {
    boolean done = false;
    for (int i = 0; i < toSort.length; i++) {
      if (done)
        break;
      done = true;

      for (int j = toSort.length - 1; j > i; j--) {
        if (toSort[j].hitScore > toSort[j - 1].hitScore) {
          TiffanysMove temp = toSort[j];
          toSort[j] = toSort[j - 1];
          toSort[j - 1] = temp;
          done = false;
        }
      }
    }
  }

}
